<meta charset="UTF-8">
<title>百词刷 - 刷单词神器 - 单词快速过遍数是记忆的好方法</title>

<h1 class=light>百词刷 刷单词神器 v0.9.3</h1>
<!--
v0.1 基本可用
v0.2 键盘方向键前后查看
v0.3 +添加误删单词
v0.4 顶部添加2nd开始背单词按钮；url传入页码；http://ielts.dawneve.cc/dict/scanWord.html?page=2
v0.5 卡片背景更绚丽，能根据颜色变文字颜色；
v0.6 支持更多目的刷单词
v0.7 排序新增viewed字段，用于筛选单词时排序
v0.8 增加灰色背景机会到一半
v0.9 修订帮助
v0.9.1 web页添加Aim列表
v0.9.2 反馈页隐藏无关提示;
v0.9.3 改名叫百词刷


# 使用方法：
1.需要修改后端sql语句 
# 扫描单词接口：返回dict格式数据
@app.route('/api/wordScan/', methods=['GET'])
def wordJson():

2. 支持给后端反馈。


# 整体架构：
RESTful API 接口模式，前后端分离。
前端: html页面
后端: python3 flask + mysql, restful API, 支持跨域访问;


改后台为php也很容易，只需要按照原接口实现功能即可，前台不需要改动。




bug: 299行 有空格的词组会报错。

-->
<div id=aim class=light></div>
<div id=control>
	1.选要背的单词，不要勾选已经会的单词。
	<hr>
	<button id=sellectAll>全选</button>
	<button id=sellectOther>反选</button>
	<button id=begin2>开始背单词</button>
</div>
<div id=word></div>

<hr>
<span id=caption2>2.一个一个刷遍数</span>
<button id=begin>开始背单词</button>

<div id=status class=light></div>


<div id=help>
<pre>
帮助：<hr>
单词不够1.5w，阅读和听力会很受障碍。听力即便认识的单词都听懂，依然会被陌生词汇分神或者误导。
过遍数是记单词的重要方法。一天花2个10min过100个单词，1个10min复习昨天的100个，学习1万单词需要100天，合计100*0.5=50h。

使用方法：
1.通过全选、反选，勾选陌生单词；
2.一对一模式开始背单词。

推荐操作：
1.全选，点击开始背单词；
2.遇到会的就按减号-从在被列表删除该单词；
不小心误删了，按下加号+，把误删的单词添加到在背单词末尾；
不会的就按下箭头，下一个循环还会遇到它；
3.改变url中的page参数，能获得单词表的不同部分；
4.改变url中的aim参数，能按照不同的策略筛选单词；




操作说明:
1.显示意思：
浏览模式：单击显示意思；
卡片模式：单击显示意思；固定时间后显示释义; 空格切换是否显示释义；鼠标移入卡片显示意思，移出卡片不显示 意思；
2.卡片模式，切换下一个单词：
- 数字键1或者 减号- 表示记住了，从背诵列表删除掉该单词，并更新进度条；
- 数字2-4，或者箭头 下 或 右，表示没记住，并显示下一个卡片；
- 箭头 上 或者 左，显示上一个单词。

3.卡片背诵模式，有单词没记住，错误的点了1，使用F12控制台，输入 oWords.todo.push('theWord') 回车即可；
或者 按加号+，只可以连续操作一次！
4.获取没记住的单词列表，F12控制台输入： oWords.todo
</pre>
</div>

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<style>
ol{padding-inline-start: 0px;}
ol li{padding:10px; list-style-position:inside;}
ol li.even{background:#eee;}
ol li span{ /*font-weight:bold;*/ padding:0 20px;display:none; }
ol li span.light3{display:inline; color:#ccc;padding:0 5px;}

.card{
	/*
	border:1px dotted #eee;
	*/
	background:rgba(240,240,240,0.5);
	border-radius:5px;
	
	height: 200px;
    width: 400px;
	padding:10px;
}
.card span,
.card input{display:none;}
.light{color:#eee;}
.light2{color:#ccc;}

#help{color:yellow; background:black;padding:2px 10px;margin-top:10px;}


.dictLink{color:#ccc; float: right; text-decoration: none; font-size:smaller;}
.dictLink:hover{text-decoration: underline;}

ol li a{text-decoration: none; color:orange;}
ol li a:hover{text-decoration: underline;}
</style>

<script>
//全局变量
var oWords={
	'keyEnabled':false, //是否答题模式
	'cur':0, //当前位置
	'todo':[], //不会的单词
	'len':0, //总单词个数
	'timeOut':1,//几秒后显示单词意思，卡片模式下。
	'repeates':0, //重复过的遍数
	'bgcolors':['#FFD4D4','#FFE0D0','#FAFBB6', '#DEFFDB', '#D7FBFF','#DEE2FF','#FBD7FF','#eeeeee',
		'#414141','#A12D36', '#FF8350', '#80A6F0','#3EBBC7','#FF7B1D','#27AE61','#DC2F2F','#12B1CF', //dark color 
		//淘汰的颜色 FEC519音标的区分度太低
	], //卡片背景颜色，要深或浅，要6位。目前看浅色好看一点。
	'color':'#eee', //当前背景色
	'times':{}, //记录每个单词循环的次数，最后循环1遍的加3分熟悉度，2遍的加1分熟悉度，其余不加；最后todo中的加陌生度1分；
	'pointA':0, //记录开始时间
	'pointB':0, //记录结束时间
};


//tools 
//获取url中的参数 https://www.cnblogs.com/vicky-li/p/9145026.html
function getUrlParam(name) {
	var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
	var r = window.location.search.substr(1).match(reg);  //匹配目标参数
	if (r != null) return unescape(r[2]); return null; //返回参数值
}
//1.js将16进制颜色转换成RGB格式
//https://blog.csdn.net/u012255097/article/details/78772157
function hexToRgb(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);   
    return result ? {       
        r: parseInt(result[1], 16),              
        g: parseInt(result[2], 16),       
        b: parseInt(result[3], 16)   
    } : null;
}

//2. 通过rgb值判断颜色深浅 https://blog.csdn.net/qq_36843675/article/details/79448051
//通过把 RGB 模式转换成 YUV 模式，而 Y 是明亮度（灰阶），因此只需要获得 Y 的值而判断他是否足够亮就可以了：
//越小颜色越深
var Y=function(obj){
	var Y=obj.r*0.299 + obj.g*0.578 + obj.b*0.114;
	return Y>192;
}



$().ready(function(){
	//给obj添加点击时隐藏或显示单词功能
	function showOnClick(obj){
		obj.click(function(){
			var oSpan=this.getElementsByTagName('span')[0];
			var oInput=this.getElementsByTagName('input')[0];
			//
			if(oSpan.style.display=='inline'){
				oSpan.style.display='none';
				oInput.checked=false;
			}else{
				oSpan.style.display='inline';
				oInput.checked=true;
			}
		})
	}
	
	//给obj添加鼠标悬停时隐藏或显示单词功能
	function showOnEnter(obj){
		obj.on('mouseenter',function(){
			var oSpan=this.getElementsByTagName('span')[0];
			oSpan.style.display='inline';
		})
		obj.on('mouseleave',function(){
			var oSpan=this.getElementsByTagName('span')[0];
			oSpan.style.display='none';
		})
	}
	
	
	//全选
	$('#sellectAll').click(function(e,i){
		var aSpan=$('#word span')
		$('#word input').each(function(index,el){
			//console.log(index, el)
			el.checked=true;
			aSpan[index].style.display='inline';
		});
	})
	
	//反选
	$('#sellectOther').click(function(e,i){
		var aSpan=$('#word span')
		$('#word input').each(function(index,el){
			el.checked=el.checked==true?false:true;
			aSpan[index].style.display=el.checked==true?'inline':'none';
		});
	})
	
	
	//初始化，加载单词
	//自动翻页功能，这样就不用太过于频繁的修改py源码了
	var page=getUrlParam('page');
	page=page||1;
	var aim=getUrlParam('aim'); //背单词目标设定
	aim=aim||-1;
	
	//如果aim没有设置，则给出aim列表
	//http://127.0.0.1:20180/api/wordScan/?get=1
	if(-1==aim){
		$.get('http://127.0.0.1:20180/api/wordScan/?get=1', function(res,status){
			//console.log('load aims: ', res);
			//清空界面： 隐藏掉多余按钮
			$('#control').html('<span class=light>Click to choose an aim: </span>');
			$('#begin').css('display','none');
			$('#caption2').css('display','none');
			//$('#help').css('display','none');
			
			$("#word").html("<ol></ol>");
			var j=1;
			for(var i in res){
				var even=j++%2==0?"class=even":"";
				//console.log(i,res[i])
				$("#word ol").append("<li "+even+"><a href=?page=1&aim="+i+">[ Aim="+i+" ] "+res[i]+"</a></li>");
			}
		});
		
		//如果显示aim列表了，则直接返回，剩下的代码不执行。
		return false;
	}
	
	
	//如果设置了aim，就可以执行了
	if(aim<0)aim=0;
	//加载后获取资源
	$.get('http://127.0.0.1:20180/api/wordScan/?page='+page+'&aim='+aim, function(res,status){
		console.log('load word: ', status);
		//res=JSON.parse(res)
		console.log('page=',page, '; aim=', aim,  res.aim)
		//web显示aim
		$("#aim").html('aim='+aim+", "+res.aim);
		
		oWords['words']=res.data;//给全局变量
		var oOl=$('<ol></ol>');
		for(var word in oWords.words){
			oWords.len++;
			var info=oWords.words[word];
			var phonetic=info.phonetic?info.phonetic:"";
			var even=oWords.len%2==0?' class=even':'';
			var oLi=$('<li'+even+'><input type=checkbox name='+word+'>'+word+' <span><a class=light2>'+phonetic+"</a> "+info.meaning+"</span></li>");
			showOnClick(oLi);//添加单击事件
			oOl.append(oLi);
			//初始化每个单词过的遍数
			oWords.times[word]=0;
		}
		$('#word').append(oOl);
		
		//默认直接全选
		$('#sellectAll').trigger('click');
	})
	
	//开始背单词按钮
	$('#begin,#begin2').click(function(){
		$('#word input').each(function(index,el){
			//获取要背诵的单词
			if(el.checked){
				var word=el.name;
				oWords.todo.push(word);
			}
			oWords['ulen']=oWords.todo.length;//unknown length
		})
		//如果没有勾选，则啥都不做
		if(oWords['ulen']==0){
			alert('没有勾选任何单词！')
			return false;
		}
		//对单词打乱
		oWords.todo.sort(function(a,b){
			return 0.5 - Math.random()
		});
		//背景色随机排序
		oWords.bgcolors.sort(function(a,b){
			return 0.5 - Math.random()
		});
		
		//清空界面
		$('#control').html('<span class=light>卡片背诵模式： 键盘操作(空格 显示意思)<hr>1记住了;2稍熟悉;3见过;4未知 </span>');
		$('#begin').css('display','none');
		$('#caption2').css('display','none');
		$('#help').css('display','none');
		
		//生成卡片
		oWords['keyEnabled']=true;//开始响应键盘事件
		refresh()
		
		//开始记录时间
		oWords['pointA']=new Date().getTime();
	})
	
	//响应键盘事件
	document.body.onkeydown=function(e){
		//console.log(e.key)
		if(!oWords.keyEnabled && e.key!='F12'){
			alert('非背诵模式，不接收键盘事件')
			return false;
		}
		//
		if(e.key=='F5'){
			if(!e.shiftKey){
				alert('refresh only shift+F5')
				//e.preventDefault()
				//e.cancelBubble = true; 
				return false;
			}
		}
		
		//下一个: 1 记住了就删掉
		if(['1','-'].indexOf(e.key)!=-1){
			var word=oWords.todo[oWords.cur];
			var tmp=oWords.todo.splice(oWords.todo.indexOf(word),1);
			var info=oWords.words[tmp[0]];
			//console.log(word,info)
			var phonetic=info.phonetic?info.phonetic:"";
			console.log(tmp[0],phonetic, info.meaning);
			oWords['last']=tmp[0];//保留最后一个被删除的单词，方便恢复
			refresh();
		}
		//下一个: 没记住，一会接着看
		else if(['2','3','4','ArrowDown','ArrowRight'].indexOf(e.key)!=-1 ){
			oWords.cur+=1;
			refresh();
		}
		//上一个: 查看
		else if(['ArrowUp','ArrowLeft'].indexOf(e.key)!=-1 ){
			oWords.cur-=1;
			refresh();
		}
		//+: 添加单词
		else if(['+'].indexOf(e.key)!=-1 ){
			if(oWords['last']){
				oWords.todo.push(oWords['last']);
				console.log('在背单词列表 末尾 添加 ', oWords['last']);
				delete oWords.last;
				//refresh();
			}else{
				console.log('无操作');
			}
		}
		//如果是空格，则意思 显示或者隐藏
		else if(e.key==' '){
			if($('#word span').css('display')=='none'){
				$('#word span').css('display','inline');
			}else{
				$('#word span').css('display','none');
			}
		}
	}
	
	//更新卡片
	function refresh(){
		//清除没有及时发生的计时器
		clearTimeout(oWords.timer);
		//进度条
		var leftPct=Math.round(100*(1-oWords.todo.length/oWords.ulen));
		//console.log('todo:', oWords.todo.length)
		$('#status').html('Current/Left: '+oWords.cur +"/"+oWords.todo.length)
		$('#status').append('<br>Remember Percentage: '+leftPct+'%<div style="height:5px;width:100%;background:red"><span style="height:100%;width:'+leftPct+'%;background:#eee;display:block;"></span></span>')
		
		//判断是否背完了
		if(oWords.todo.length<=10){
			if(oWords.todo.length==10 && oWords['pointB']==0){
				alert('任务基本完成! 请看控制台: 反馈刷新再开一局 or 直接刷新再来一遍?');
				oWords['pointB']=new Date().getTime();
			}
			//给后台反馈
			console.log('%cTips: 反馈并记录到后台: $(document).trigger("feedback"); %c //第一面斩杀熟悉度加3分，第二面斩杀熟悉度加1分；最后剩下的单词(oWords.todo中的)不熟悉度加1分;','color:red;','color:#aaaaaa');
			
			if(oWords.todo.length==0){
				console.log('任务完成！请F12反馈到后台，或者F5刷新，再开一局!');
				return false;
			}
		}
		//循环背诵，到最后一个
		if(oWords.cur>=oWords.todo.length){
			oWords.cur=0;
			console.log('%c打乱单词，再过一遍。已刷遍数：'+ (++oWords.repeates), 'background:black; padding:5px;color:'+ oWords.color );
			//对单词打乱
			oWords.todo.sort(function(a,b){
				return 0.5 - Math.random()
			});
		}
		
		//循环背诵，到第一个
		if(oWords.cur<0){
			oWords.cur=oWords.todo.length-1;
		}
		
		//准备卡片
		var word=oWords.todo[oWords.cur];
		var info=oWords.words[word];
		//console.log(word,info)
		var phonetic=info.phonetic?info.phonetic:"";
		var wordUrl='<a class="dictLink" target=_blank href="/dict/index.html?word='+word+'">拂晓词典</a>'
		var oLi=$('<div class=card><input type=checkbox name='+word+'>'+wordUrl+'<b>'+word+'</b> <p class=light2>'+phonetic+'</p><span>'+info.meaning+"</span></div>");
		showOnClick(oLi);//添加单击显示
		showOnEnter(oLi) //鼠标悬停显示
		oWords['timer']=setTimeout(function(){
			//触发点击事件：显示释义
			//https://blog.csdn.net/MiaoHuiZiJiDeGuShi/article/details/84074559
			//https://www.cnblogs.com/jiangxiaobo/p/5830200.html
			//https://blog.csdn.net/bcfdsagbfcisbg/article/details/78480307
			oLi.trigger("click");
		},oWords.timeOut*1000)

		$('#word').html(oLi);//显示卡片
		oWords.times[word]=oWords.repeates; //该单词显示遍数和当前过的遍数一致;
		
		//从第二遍开始，卡片变色生效;
		if(oWords.repeates>0){
			//换卡片的颜色
			oWords.color=oWords.bgcolors[ oWords.repeates % oWords.bgcolors.length ];
			//如果是计数次，必须是灰色背景
			if(oWords.repeates %2==0){
				oWords.color="#eeeeee";
			}
			$('#word .card').css('background', oWords.color)
			//设置卡片文字的颜色
			$('#word .card').css('color', Y( hexToRgb(oWords.color) )?"black":"white" );
		}
	}
	
	//给后台反馈数据：1或者2次浏览的单词(浏览过0和1次，熟悉度加3和1分)，以及最后剩下到todo中的单词(陌生度加1分)
	function feedback(){
		//构建要反馈的数据，熟悉的单词
		var oFamiliar={'g3':[], 'g1':[],'viewed':[]}
		for(var word in oWords.times){
			var times=oWords.times[word];
			if(times==0){
				oFamiliar['g3'].push(word);//加3分
			}else if(times==1){
				oFamiliar['g1'].push(word);//加1分
			}
			//
			oFamiliar['viewed'].push(word); //所有单词的看过一遍, viewed字段都加一分
		}
		//不熟悉的单词
		var aUnfamiliar=oWords.todo;
		
		//*********************
		
		//开始向后台传递
		console.log('sendToPy: ',oFamiliar, aUnfamiliar)
		//如果已经传递过一次了，则啥都不干
		if(oWords['feedbacked']!=undefined){
			console.log('%c这次啥也没干，因为此前已提交过。','color:red;', '； 想再次提交，请控制台输入: delete oWords.feedbacked; 后再试。')
			return false;
		}
		
		/*
		tmp1=JSON.stringify(oFamiliar)
		tmp2=JSON.stringify(aUnfamiliar)
		console.log('InJS: tmp1=',tmp1, 'tmp2='.tmp2)
		*/
		console.log('提交中，请稍候...');
		$.post('http://127.0.0.1:20180/api/wordScanFeedback/',{
			'plus':JSON.stringify(oFamiliar), //tmp1,
			'minus':JSON.stringify(aUnfamiliar), //tmp2,
		},function(res,status){
			oWords['feedbacked']=true;//已经向后台提交过
			//console.log('FromPy:', res,status)
			
			//清空界面
			//$('#control').html('<hr>');
			$('#control').html('<span class=light>列表浏览模式： 难背的单词，请使用响应模式继续刷。</span>');
			
			//刷新页面，显示最后几个单词列表
			if(oWords.pointB==0){
				oWords.pointB=new Date().getTime();
			}
			$('#word').html("<span class=light2>本次刷词已记录到数据库! 本次卡片耗时: "+ ((oWords.pointB-oWords.pointA)/1000/60).toFixed(2)+"min <br>难搞定 word list:</span><hr>");
			var oOl=$('<ol></ol>');
			var i=0;
			for(var word in res.data.unfamiliarScore){
				var info=oWords.words[word];
				var even=(++i)%2==0?' class=even':'';
				var phonetic=info.phonetic?info.phonetic:"";
				var wordUrl='<a target=_blank href="/dict/index.html?word='+word+'">'+word+'</a>'
				var oLi=$('<li'+even+'>'+wordUrl+" <span class=light3>"+info.phonetic+'</span> <span class=light3>'+info.meaning+"</span></li>");
				oOl.append(oLi);
			}
			$('#word').append(oOl);
			$('#word').append("<hr><span style='color:red'>少侠，请 shift+F5 刷新，再开一局!</span>");
			console.log('Done!');
		})
	}
	//自定义事件
	$(document).on('feedback',feedback)
	//触发提交
	//$(document).trigger('feedback')
	
})
</script>